package org.quincy.rock.core.dao;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.mutable.MutableInt;
import org.quincy.rock.core.dao.EntityMetadata.EntityTable;
import org.quincy.rock.core.dao.EntityMetadata.PropDescr;
import org.quincy.rock.core.dao.sql.Match;
import org.quincy.rock.core.dao.sql.Op;
import org.quincy.rock.core.dao.sql.Predicate;
import org.quincy.rock.core.function.RtEachConsumer;
import org.quincy.rock.core.function.RtFunction;
import org.quincy.rock.core.util.StringUtil;
import org.springframework.jdbc.support.JdbcUtils;

/**
 * <b>DaoUtil。</b>
 * <p><b>详细说明：</b></p>
 * <!-- 在此添加详细说明 -->
 * 无。
 * <p><b>修改列表：</b></p>
 * <table width="100%" cellSpacing=1 cellPadding=3 border=1>
 * <tr bgcolor="#CCCCFF"><td>序号</td><td>作者</td><td>修改日期</td><td>修改内容</td></tr>
 * <!-- 在此添加修改列表，参考第一行内容 -->
 * <tr><td>1</td><td>wks</td><td>2018年4月20日 下午2:50:28</td><td>建立类型</td></tr>
 * 
 * </table>
 * @version 1.0
 * @author wks
 * @since 1.0
 */
public abstract class DaoUtil {

	/**
	 * <b>迭代结果集。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param rs ResultSet
	 * @param consumer 回调函数
	 * @throws SQLException
	 */
	public static int forEach(ResultSet rs, RtEachConsumer<Map<String, Object>> consumer) throws SQLException {
		ResultSetMetaData meta = rs.getMetaData();
		int count = meta.getColumnCount();
		String[] names = new String[count];
		for (int i = 0; i < count; i++) {
			names[i] = JdbcUtils.lookupColumnName(meta, i + 1);
		}
		return forEach(rs, consumer, names);
	}

	/**
	 * <b>迭代结果集。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param rs ResultSet
	 * @param consumer 回调函数
	 * @param 列名数组
	 * @throws SQLException
	 */
	public static int forEach(ResultSet rs, RtEachConsumer<Map<String, Object>> consumer, String... colNames)
			throws SQLException {
		int index = 0;
		while (rs.next()) {
			Map<String, Object> row = new HashMap<>();
			for (String colName : colNames) {
				row.put(colName, JdbcUtils.getResultSetValue(rs, rs.findColumn(colName)));
			}
			consumer.each(index++, row);
		}
		return index;
	}

	/**
	 * <b>根据实体元数据生成select语句。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param meta 实体元数据
	 * @param distinct
	 * @param excluded 排除的属性
	 * @return select语句
	 */
	public static StringBuilder getSelectSQL(EntityMetadata meta, boolean distinct, String... excluded) {
		return getSelectSQL(meta, distinct, NameInterceptor.NOOP, excluded);
	}

	/**
	 * <b>根据实体元数据生成select语句。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param meta 实体元数据
	 * @param distinct
	 * @param nameInterceptor name拦截器。
	 * @param excluded 排除的属性
	 * @return select语句
	 */
	public static StringBuilder getSelectSQL(EntityMetadata meta, boolean distinct,
			final NameInterceptor nameInterceptor, String... excluded) {
		//select
		StringBuilder sb = new StringBuilder("select ");
		if (distinct)
			sb.append("distinct ");
		EntityTable et = meta.entityTable();
		//迭代字段
		meta.persistNames((name) -> {
			if (meta.canWrite(name) && !ArrayUtils.contains(excluded, name)) {
				String colName = meta.columnName(name);
				if (meta.isCalculateColumn(name))
					sb.append(colName);
				else {
					String tableAlias = meta.tableAlias(name);
					sb.append(nameInterceptor.applyColumnName(
							StringUtils.isBlank(tableAlias) ? meta.tableAlias() : tableAlias,
							StringUtils.isBlank(colName) ? name : colName));
				}
				sb.append(StringUtil.CHAR_SPACE).append(nameInterceptor.applyPropertyName(name))
						.append(StringUtil.CHAR_COMMA);
			}
		});
		et.extraColumn((alias, colName) -> {
			sb.append(nameInterceptor.applyColumnName(alias, colName)).append(StringUtil.CHAR_COMMA);
		});
		et.extraExprColumn((alias, expr) -> {
			sb.append(expr).append(StringUtil.CHAR_COMMA);
		});
		sb.deleteCharAt(sb.length() - 1).append(StringUtil.CHAR_SPACE);
		//from
		et.build(sb, nameInterceptor, ValueInterceptor.NOOP);
		return sb;
	}

	/**
	 * <b>根据实体元数据生成insert语句。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param meta 实体元数据
	 * @param vo 值对象
	 * @param ignoreNullValue 是否忽略null
	 * @param valueGetter 字段值获得拦截器
	 * @param excluded 排除的属性
	 * @return insert语句
	 */
	public static StringBuilder getInsertSQL(EntityMetadata meta, Object vo, boolean ignoreNullValue,
			RtFunction<String, CharSequence> valueGetter, String... excluded) {
		return getInsertSQL(meta, vo, ignoreNullValue, valueGetter, NameInterceptor.NOOP);
	}

	/**
	 * <b>根据实体元数据生成insert语句。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param meta 实体元数据
	 * @param vo 值对象
	 * @param ignoreNullValue 是否忽略null
	 * @param valueGetter 字段值获得拦截器
	 * @param nameInterceptor name拦截器
	 * @param excluded 排除的属性
	 * @return insert语句
	 */
	public static StringBuilder getInsertSQL(EntityMetadata meta, Object vo, boolean ignoreNullValue,
			RtFunction<String, CharSequence> valueGetter, final NameInterceptor nameInterceptor, String... excluded) {
		List<CharSequence> fList = new ArrayList<>(meta.propertyCount());
		List<CharSequence> vList = new ArrayList<>(meta.propertyCount());
		String pkName = meta.pKName();
		meta.persistNames(name -> {
			PropDescr pd = meta.entityTable().pd(name);
			if (meta.notJoinTableField(name) && pd.canRead() && !pd.ignoreInsert()
					&& !ArrayUtils.contains(excluded, name)) {
				boolean insert = false;
				if (name.equals(pkName)) {
					if (!meta.isNullPropertyValue(vo, name)) {
						insert = true;
					}
				} else if (ignoreNullValue) {
					if (!meta.isNullPropertyValue(vo, name)) {
						insert = true;
					}
				} else {
					insert = true;
				}
				if (insert) {
					String colName = meta.columnName(name);
					fList.add(nameInterceptor.applyColumnName(colName == null ? name : colName));
					vList.add(valueGetter.apply(name));
				}
			}
		});
		//
		String tableName = meta.tableName();
		tableName = nameInterceptor.applyTableName(tableName == null ? meta.entityName() : tableName).toString();
		StringBuilder sql = new StringBuilder("insert into ").append(tableName).append('(')
				.append(StringUtils.join(fList, StringUtil.CHAR_COMMA)).append(") values (")
				.append(StringUtils.join(vList, StringUtil.CHAR_COMMA)).append(')');
		return sql;
	}

	/**
	 * <b>根据实体元数据生成update语句。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param meta 实体元数据
	 * @param vo 值对象
	 * @param ignoreNullValue 是否忽略null
	 * @param ignorePrimaryKey 是否忽略主键
	 * @param valueGetter 字段值获得拦截器
	 * @param excluded 排除的属性
	 * @return update语句
	 */
	public static StringBuilder getUpdateSQL(EntityMetadata meta, Object vo, boolean ignoreNullValue,
			boolean ignorePrimaryKey, RtFunction<String, CharSequence> valueGetter, String... excluded) {
		return getUpdateSQL(meta, vo, ignoreNullValue, ignorePrimaryKey, valueGetter, NameInterceptor.NOOP);
	}

	/**
	 * <b>根据实体元数据生成update语句。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param meta 实体元数据
	 * @param vo 值对象
	 * @param ignoreNullValue 是否忽略null
	 * @param ignorePrimaryKey 是否忽略主键
	 * @param valueGetter 字段值获得拦截器
	 * @param nameInterceptor name拦截器
	 * @param excluded 排除的属性
	 * @return update语句
	 */
	public static StringBuilder getUpdateSQL(EntityMetadata meta, Object vo, boolean ignoreNullValue,
			boolean ignorePrimaryKey, RtFunction<String, CharSequence> valueGetter,
			final NameInterceptor nameInterceptor, String... excluded) {
		List<CharSequence> list = new ArrayList<>(meta.propertyCount());
		String pkName = meta.pKName();
		meta.persistNames(name -> {
			PropDescr pd = meta.entityTable().pd(name);
			if (meta.notJoinTableField(name) && pd.canRead() && !pd.ignoreUpdate()
					&& !ArrayUtils.contains(excluded, name) && (!ignorePrimaryKey || !name.equals(pkName))) {
				boolean update = false;
				if (ignoreNullValue) {
					update = !meta.isNullPropertyValue(vo, name);
				} else {
					update = true;
				}
				if (update) {
					String colName = meta.columnName(name);
					CharSequence setV = new StringBuilder(
							nameInterceptor.applyColumnName(colName == null ? name : colName))
									.append(StringUtil.CHAR_EQUALS).append(valueGetter.apply(name));
					list.add(setV);
				}
			}
		});
		//
		String tableName = meta.tableName();
		StringBuilder sql = new StringBuilder("update ")
				.append(nameInterceptor.applyTableName(tableName == null ? meta.entityName() : tableName))
				.append(" set ").append(StringUtils.join(list, StringUtil.CHAR_COMMA));
		return sql;
	}

	/**
	 * <b>生成where in sql子句。</b>
	 * <p><b>详细说明：</b></p>
	 * <!-- 在此添加详细说明 -->
	 * 无。
	 * @param name 字段名
	 * @param it 值列表
	 * @return where in 子句
	 */
	public static String whereInById(CharSequence name, Iterator<? extends Number> it) {
		StringBuilder where = new StringBuilder(name);
		where.append(" in (");
		if (it.hasNext()) {
			where.append(it.next());
		}
		while (it.hasNext()) {
			where.append(',');
			where.append(it.next());
		}
		where.append(")");
		return where.toString();
	}

	public static String likeString(CharSequence v, Match match) {
		StringBuilder sb = new StringBuilder();
		switch (match) {
		case FULL:
			sb.append('%').append(v).append('%');
			break;
		case LEFT:
			sb.append('%').append(v);
			break;
		case RIGHT:
			sb.append(v).append('%');
			break;
		default:
			sb.append(v);
			break;
		}
		return sb.toString();
	}

	public static String whereString(EntityMetadata meta, Predicate where, boolean applyAlias,
			NameInterceptor nameInterceptor, Map<String, Object> paramMap, String mapName, CharSequence open,
			CharSequence close) {
		MutableInt index = new MutableInt(0);
		return meta.where(where, applyAlias, nameInterceptor, (v, dt, match) -> {
			StringBuilder varName = new StringBuilder();
			if (!StringUtil.isBlank(mapName))
				varName.append(mapName).append(StringUtil.CHAR_DOT);
			varName.append("_w_").append(index.incrementAndGet());
			if (match != null)
				v = likeString((String) v, match);
			paramMap.put(varName.toString(), v);
			return new StringBuilder(open).append(varName).append(close);
		});
	}

	public static Predicate not() {
		return new Predicate().not(true);
	}

	public static Predicate and() {
		return new Predicate().op(Op.AND);
	}

	public static Predicate or() {
		return new Predicate().op(Op.OR);
	}
}
